using System;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using CustomToolGenerator;
using EnvDTE;
using Neo.Generator.CodeGen;
using Neo.MetaModel.Reader;
using Webbness.Generator.CodeDom;
using Webbness.VsTool.Interfaces;

namespace Webbness.VsTool
{
    [Guid("E51E2EE0-8672-4bc9-963D-C167BFE6D765")]
	public class CodeGenAdaptor : BaseCodeGeneratorWithSite
	{
		private readonly string[] folderGUIDs = new string[3]{"{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}", "{6BB5F8F0-4483-11D3-8BCF-00C04F8EC28C}", "{66A26722-8FB5-11D2-AA7E-00C04F688DDE}"};
		private string[] _overwriteArray = new string[0];
		
        protected override byte[] GenerateCode(string path, string contents)
        {
            StringWriter writer;
            CodeGenerator codeGen;
            byte[] output;
            //bool genAdmin = false;
			bool justTheSupportClasses = true;
            writer = new StringWriter();

            ValidModelFactory factory = new ValidModelFactory();
            ValidatingModel validmodel = factory.Create(path);
            if (validmodel.IsValid)
            {
            	if (validmodel.Properties.ContainsKey("Debug") && validmodel.Properties["Debug"].Equals("true", StringComparison.InvariantCultureIgnoreCase) )
            	{
            		System.Diagnostics.Debugger.Launch();
            	}
            	
                //if (validmodel.Properties.ContainsKey("GenAdmin") && validmodel.Properties["GenAdmin"].Equals("true", StringComparison.InvariantCultureIgnoreCase) )
                //{
                //    genAdmin = true;
                //}
            	
            	if (validmodel.Properties.ContainsKey("supportOnly") && validmodel.Properties["supportOnly"].Equals("false", StringComparison.InvariantCultureIgnoreCase) )
            	{
            		justTheSupportClasses = false;
            	}
            	
            	if (validmodel.Properties.ContainsKey("forceOverwrite"))
            	{
            		processOverwrites(validmodel.Properties["forceOverwrite"]);
            	}
            	else
            	{
            		_overwriteArray = new string[0];
            	}
            	
            	if (validmodel.HasForcedOverwrite)
            	{
            		if (System.Windows.Forms.DialogResult.OK != MessageBox.Show("The following files will be overwritten:" + System.Environment.NewLine + validmodel.Message + System.Environment.NewLine + "Do you want to continue?", "Files will be Replaced!", MessageBoxButtons.OKCancel))
            		{
            			return null;
            		}
            	}
            	
            	if (_overwriteArray.Length > 0)
            	{
            		if (System.Windows.Forms.DialogResult.OK != MessageBox.Show("All the files of the following types will be overwritten:" + System.Environment.NewLine + String.Join(System.Environment.NewLine, _overwriteArray) + System.Environment.NewLine + "Do you want to continue?", "Files will be Replaced!", MessageBoxButtons.OKCancel))
            		{
            			return null;
            		}
            	}
            	
            	ProjectItem modelItem = base.GetService(typeof (ProjectItem)) as ProjectItem;
            	
            	if (modelItem == null)
                {
                	MessageBox.Show("Unable to locate a ProjectItem based on the model.", "WebbnessCodeGen");
                	writer.Write("/* Unable to locate a ProjectItem based on the model. */");
                   	output = Encoding.ASCII.GetBytes(writer.ToString());
            		writer.Close();
            		return output;
                }
                
            	EnvDTE.StatusBar sbar = modelItem.ContainingProject.DTE.Application.StatusBar;

            	string modelFolder = Path.GetDirectoryName(path);
                string solutionFolder = Path.GetDirectoryName(modelItem.DTE.Solution.FullName);
                string resourceFolder = Path.Combine(solutionFolder, "Templates");
                
                codeGen = new CodeGenerator();
                codeGen.ReaderType = typeof (NorqueReader);
                codeGen.ResourcePath = resourceFolder;

                try
                {
                    codeGen.GenerateSupportClasses(path, writer);
                    sbar.Text = "Generated the support classes...";
                    if (justTheSupportClasses)
                    {
                    	output = Encoding.ASCII.GetBytes(writer.ToString());
            			writer.Close();
            			return output;
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "WebbnessCodeGen");
                    writer.WriteLine("/* WebbnessCodeGen caught an exception while generating the source code: {0}\n\n{1}\n*/", e.Message, e.StackTrace);
                }
                try
                {
                    IList modelFileList;

                    modelFileList = codeGen.GenerateClassFiles(path, modelFolder, true, getOverwrite("model"));
                    
                    sbar.Text = "Generated the model classes...";
					Application.DoEvents();
					
                    if (modelItem == null)
                    {
                        throw new ApplicationException("Generated/updates files but cannot automatically add new files to project.");
                    }
                    foreach (string file in modelFileList)
                    {
                        AddFileToProjectIfNecessary(modelItem.ContainingProject, file);
                    }
					
                    codeGen = new CodeGenerator();
                	codeGen.ReaderType = typeof (NorqueReader);
                	codeGen.ResourcePath = resourceFolder;
                	codeGen.UserClassTemplate = "NeoSurrogate.vtl";
                	codeGen.UserFileExtension = "Surrogate.cs";
                    IList surrogateFileList;

                    surrogateFileList = codeGen.GenerateClassFiles(path, modelFolder, true, getOverwrite("surrogate"));

                    if (modelItem == null)
                    {
                        throw new ApplicationException("Generated/updates files but cannot automatically add new files to project.");
                    }
                    foreach (string file in surrogateFileList)
                    {
                        AddFileToProjectIfNecessary(modelItem.ContainingProject, file);
                    }
                    
                    sbar.Text = "Generated the surrogate classes...";
                    Application.DoEvents();
                    
                    EnvDTE.Project viewsProject = null;
                    EnvDTE.Project controllersProject = null;
                    EnvDTE.Project testsProject = null;
                    EnvDTE.Project solutionItemsProject = null;
                    
                    foreach (EnvDTE.Project project in modelItem.DTE.Solution.Projects)
                    {
                    	if (project.Name.EndsWith("Controllers"))
                    		controllersProject = project;
                    	else if (project.Name.EndsWith("Views"))
                    		viewsProject = project;
                    	else if (project.Name.EndsWith("UnitTests"))
                    		testsProject = project;
                    	else if (project.Name.Equals("Solution Items"))
                    		solutionItemsProject = project;
                    	
                    }
                    
                    if (controllersProject != null)
                    {
                        codeGen = new CodeGenerator();
                        codeGen.ReaderType = typeof(NorqueReader);
                        codeGen.ResourcePath = resourceFolder;
                        codeGen.UserClassTemplate = "NeoController.cs.vtl";
                        codeGen.UserFileExtension = "ControllerImpl.cs";

                        foreach (string file in codeGen.GenerateClassFiles(path, Path.GetDirectoryName(controllersProject.FullName), true, getOverwrite("controller")))
                        {
                            AddFileToProjectIfNecessary(controllersProject, file);
                        }

                        sbar.Text = "Generated the controller classes...";
                        Application.DoEvents();

                        codeGen = new CodeGenerator();
                        codeGen.ReaderType = typeof(NorqueReader);
                        codeGen.ResourcePath = resourceFolder;
                        codeGen.UserClassTemplate = "NeoBaseController.cs.vtl";
                        codeGen.UserFileExtension = "Base.cs";

                        foreach (string file in codeGen.GenerateClassFiles(path, Path.Combine(Path.GetDirectoryName(controllersProject.FullName), "Base"), true, true))
                        {
                            AddFileToProjectIfNecessary(controllersProject, file);
                        }

                        sbar.Text = "Generated the base controller classes...";
                        Application.DoEvents();
                        
                    	ISingleTemplateCreator helpers = new HelperFileCreator();
                    	helpers.CreateFolders = true;
                    	helpers.ForceOverwrite = getOverwrite("helper");
                    	helpers.OutputFolder = Path.Combine(Path.GetDirectoryName(controllersProject.FullName), "Helpers");
                    	helpers.Model = validmodel.Model;
                    	helpers.TemplatesFolder = resourceFolder;
                    	helpers.Template = "NeoHelper.cs.vtl";
                    	helpers.ProjectName = controllersProject.Name;
                    	helpers.Run();
                    	foreach (string file in helpers.CreatedFiles)
                    	{
                    		AddFileToProjectIfNecessary(controllersProject, file);
                    	}
                    
                    	sbar.Text = "Generated the helper classes...";
                    	Application.DoEvents();

                    	/*
                    	ISingleTemplateCreator components = new ComponentFileCreator();
                    	components.CreateFolders = true;
                    	components.ForceOverwrite = getOverwrite("component");
                    	components.OutputFolder = Path.Combine(Path.GetDirectoryName(controllersProject.FullName), "Components");
                    	components.Model = validmodel.Model;
                    	components.TemplatesFolder = resourceFolder;
                    	components.Template = "NeoComponent.cs.vtl";
                    	components.ProjectName = controllersProject.Name;
                    	components.Run();
                    	foreach (string file in components.CreatedFiles)
                    	{
                    		AddFileToProjectIfNecessary(controllersProject, file);
                    	}
                    
                    	sbar.Text = "Generated the component classes...";
                    	Application.DoEvents();
                    	*/
                    	
                   	    ISingleTemplateCreator resourceFileCreator = new SingleTemplateFileCreator();
                    	resourceFileCreator.CreateFolders = true;
                    	resourceFileCreator.ForceOverwrite = getOverwrite("resource");
                    	resourceFileCreator.OutputFolder = String.Format("{0}\\Resources", Path.GetDirectoryName(controllersProject.FullName));
                    	resourceFileCreator.Model = validmodel.Model;
                    	resourceFileCreator.TemplatesFolder = resourceFolder;
                    	resourceFileCreator.Template = "Resource.resx.vtl";
                    	resourceFileCreator.ProjectName = controllersProject.Name;
                    	resourceFileCreator.Run();
                    	foreach (string file in resourceFileCreator.CreatedFiles)
                    	{
                    		AddFileToProjectIfNecessary(controllersProject, file);
                    	}
                    	
                    	sbar.Text = "Generated the resource files...";
                    	Application.DoEvents();
                    	
                    	ISingleTemplateCreator recursiveFileCreator = new RecursiveFileCreator();
                    	recursiveFileCreator.CreateFolders = true;
                    	recursiveFileCreator.ForceOverwrite = false;
                    	recursiveFileCreator.OutputFolder = Path.GetDirectoryName(controllersProject.FullName);
                    	recursiveFileCreator.Model = validmodel.Model;
                    	recursiveFileCreator.TemplatesFolder = Path.Combine(resourceFolder, "Controllers");
                    	recursiveFileCreator.ProjectName = controllersProject.Name;
                    	recursiveFileCreator.Run();
                    	foreach (string file in recursiveFileCreator.CreatedFiles)
                    	{
                    		AddFileToProjectIfNecessary(controllersProject, file);
                    	}
                    }
                    
                    if (viewsProject != null)
                    {
                        //ICodeDomCreator views = CodeDomCreatorFactory.Create("Views", path, GetDefaultExtension());
                        //views.OutputRoot = Path.GetDirectoryName(viewsProject.FullName);
                        //views.Force = false;
                        //views.CreateFolders = false;
                        //views.Run();
                        //foreach (string file in views.CreatedFiles)
                        //{
                        //    AddFileToProjectIfNecessary(viewsProject, file);
                        //}
						
                        //sbar.Text = "Generated the view classes...";
                        //Application.DoEvents();

                    	IMultiTemplateCreator viewTemplateCreator = new MultiTemplateFileCreator();
                    	viewTemplateCreator.CreateFolders = true;
                    	viewTemplateCreator.ForceOverwrite = getOverwrite("view");
                    	viewTemplateCreator.OutputFolder = String.Format("{0}\\Views", Path.GetDirectoryName(viewsProject.FullName));
                    	viewTemplateCreator.Model = validmodel.Model;
                    	viewTemplateCreator.TemplatesFolder = resourceFolder;
                    	viewTemplateCreator.Templates.Add("List");
                    	viewTemplateCreator.Templates.Add("New");
                    	viewTemplateCreator.Templates.Add("Detail");
                    	viewTemplateCreator.Templates.Add("Edit");
                    	viewTemplateCreator.Templates.Add("Index");
                    	viewTemplateCreator.Templates.Add("Help");
                    	//viewTemplateCreator.Templates.Add("Error");
                    	viewTemplateCreator.ProjectName = viewsProject.Name;
                    	viewTemplateCreator.Run();

                        foreach (string file in viewTemplateCreator.CreatedFiles)
                        {
                            AddFileToProjectIfNecessary(viewsProject, file);
                        }

                    	sbar.Text = "Generated the view templates...";
                    	Application.DoEvents();

                        //if (genAdmin)
                        //{
                        //    viewTemplateCreator = new MultiTemplateFileCreator();
                        //    viewTemplateCreator.CreateFolders = true;
                        //    viewTemplateCreator.ForceOverwrite = getOverwrite("view");
                        //    viewTemplateCreator.OutputFolder = String.Format("{0}\\PageTemplates\\Admin", Path.GetDirectoryName(viewsProject.FullName));
                        //    viewTemplateCreator.Model = validmodel.Model;
                        //    viewTemplateCreator.TemplatesFolder = resourceFolder;
                        //    viewTemplateCreator.Templates.Add("List");
                        //    viewTemplateCreator.Templates.Add("New");
                        //    viewTemplateCreator.Templates.Add("Detail");
                        //    viewTemplateCreator.Templates.Add("Edit");
                        //    viewTemplateCreator.Templates.Add("Index");
                        //    viewTemplateCreator.Templates.Add("Help");
                        //    //viewTemplateCreator.Templates.Add("Error");
                        //    viewTemplateCreator.ProjectName = viewsProject.Name;
                        //    viewTemplateCreator.Run();
                    		
                        //    sbar.Text = "Generated the admin view templates...";
                        //}
                    	
                    	ISingleTemplateCreator recursiveFileCreator = new RecursiveFileCreator();
                    	recursiveFileCreator.CreateFolders = true;
                    	recursiveFileCreator.ForceOverwrite = false;
                    	recursiveFileCreator.OutputFolder = Path.GetDirectoryName(viewsProject.FullName);
                    	recursiveFileCreator.Model = validmodel.Model;
                    	recursiveFileCreator.TemplatesFolder = Path.Combine(resourceFolder, "Views");
                    	recursiveFileCreator.ProjectName = viewsProject.Name;
                    	recursiveFileCreator.Run();
                    	
                    	foreach (string file in recursiveFileCreator.CreatedFiles)
                    	{
                    		AddFileToProjectIfNecessary(viewsProject, file);
                    	}
                    	
                    }
                    
                    if (testsProject != null)
                    {
                        //ICodeDomCreator tests = CodeDomCreatorFactory.Create("Tests", path, GetDefaultExtension());
                        //tests.OutputRoot = Path.GetDirectoryName(testsProject.FullName);
                        //tests.Force = getOverwrite("test");
                        //tests.CreateFolders = false;
                        //tests.Properties = validmodel.Properties;
                        //tests.Run();
                        //foreach (string file in tests.CreatedFiles)
                        //{
                        //    AddFileToProjectIfNecessary(testsProject, file);
                        //}
                    	
                        //sbar.Text = "Generated the unit test classes...";
                        //Application.DoEvents();

                    	ISingleTemplateCreator recursiveFileCreator = new RecursiveFileCreator();
                    	recursiveFileCreator.CreateFolders = true;
                    	recursiveFileCreator.ForceOverwrite = false;
                    	recursiveFileCreator.OutputFolder = Path.GetDirectoryName(testsProject.FullName);
                    	recursiveFileCreator.Model = validmodel.Model;
                    	recursiveFileCreator.TemplatesFolder = Path.Combine(resourceFolder, "UnitTests");
                    	recursiveFileCreator.ProjectName = testsProject.Name;
                    	recursiveFileCreator.Run();
                    	foreach (string file in recursiveFileCreator.CreatedFiles)
                    	{
                    		AddFileToProjectIfNecessary(testsProject, file);
                    	}
                    }
                    
                    BuildIncrementer bi = new BuildIncrementer(modelItem.DTE);
                    bi.IncrementBuildNumber();
                    string revision = bi.RevisionNumber;
                    
                    DatabaseDiff dbdiff = new DatabaseDiff();
                    dbdiff.Properties = validmodel.Properties;
                    dbdiff.Properties.Add("Revision", revision);

					dbdiff.ProjectName = "DBDiff";
					dbdiff.Model = validmodel.Model;
					dbdiff.OutputFolder = solutionFolder + "\\SQL";
					dbdiff.TemplatesFolder = resourceFolder;
					dbdiff.Template = "SqlTables.vtl";
					try
					{
						dbdiff.Run();
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.Message);
					}
					
					if (solutionItemsProject != null)
                    {
                    	foreach (string file in dbdiff.CreatedFiles)
                    	{
                    		AddFileToProjectFolder(solutionItemsProject, "SQL", file);
                    		sbar.Text = "Generated the sql update file...";
                    	}
                    }
                    
					ISingleTemplateCreator dbCreator = new SingleFileCreator();
                    dbCreator.CreateFolders = true;
                    dbCreator.ForceOverwrite = true;
                    dbCreator.Properties = validmodel.Properties;
                    dbCreator.OutputFolder = solutionFolder + "\\SQL";
                    dbCreator.Model = validmodel.Model;
                    dbCreator.TemplatesFolder = resourceFolder;
                    dbCreator.Template = "NeoCreateDb.sql.vtl";
                    dbCreator.ProjectName = "CreateDB";
                    dbCreator.Run();
                    if (solutionItemsProject != null)
                    {
                    	foreach (string file in dbCreator.CreatedFiles)
                    	{
                    		AddFileToProjectFolder(solutionItemsProject, "SQL", file);
                    		
                    	}
                    }
                    
                    sbar.Text = "Generated the sql update file...";
                    Application.DoEvents();

                    sbar.Text = "Webbness FrameWork has completed revision " + revision + ".";
                    Application.DoEvents();
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "WebbnessCodeGen");
                }
            }
            else
            {
                MessageBox.Show("The model file is invalid. Check the generated file for more information.", "WebbnessCodeGen");
                writer.WriteLine("/* The model file is invalid: {0}\n*/", validmodel.Message);
            }
            output = Encoding.ASCII.GetBytes(writer.ToString());
            writer.Close();
			
            return output;
        }

        private void processOverwrites(string overwrites)
        {
        	_overwriteArray = overwrites.Split('|');
        }

        private bool getOverwrite(string name)
        {
        	foreach(String s in _overwriteArray)
        	{
        		if (s.Equals(name, StringComparison.InvariantCultureIgnoreCase))
        			return true;
        	}
        	return false;
        }
        
        private void AddFileToProjectIfNecessary(Project project, string filename)
		{
			if(ItemsContainFile(project.ProjectItems, filename) == false)
				project.ProjectItems.AddFromFile(filename);
		}
		
        private void AddFileToProjectFolder(Project project, string folder, string filename)
		{
			foreach(ProjectItem projectItem in project.ProjectItems)
			{
				if(IsFolder(projectItem))
				{
					if (projectItem.Name.Equals(folder, StringComparison.InvariantCultureIgnoreCase))
					{
						if (projectItem.SubProject.ProjectItems.Count > 0)
						{
							foreach(ProjectItem subItem in projectItem.SubProject.ProjectItems)
							{
								if (ProjectItemRepresentsFile(projectItem, filename) == false)
								{
									projectItem.SubProject.ProjectItems.AddFromFile(filename);
									return;
								}
							}
						}
						else
						{
							projectItem.SubProject.ProjectItems.AddFromFile(filename);
						}
					}
				}
			}
			MessageBox.Show("The folder named '" + folder + "' was not found.", "Webbness Warning");
		}

		private bool ItemsContainFile(ProjectItems items, string filename)
		{
			foreach(ProjectItem projectItem in items)
			{
				if(IsFolder(projectItem))
					return ItemsContainFile(projectItem.ProjectItems, filename);
				else
					return ProjectItemRepresentsFile(projectItem, filename);
			}
			return false;
		}


		// if match check document full name in case it's the right filename wrong folder
		// NOTE: accessing the document can cause a COM exception
		private bool ProjectItemRepresentsFile(ProjectItem projectItem, string filename)
		{
			if(filename.ToLower() == projectItem.Name.ToLower())
			{
				try
				{
					Document doc = projectItem.Document;
					if(doc != null && doc.FullName != null)
					{
						if(doc.FullName.ToLower() == doc.FullName.ToLower())
							return true;
					}
				}
				catch(COMException)
				{
					// ignore it
				}
			}
			return false;
		}

		public bool IsFolder(ProjectItem item)
		{
			foreach(string guid in folderGUIDs)
			{
				if(item.Kind.ToUpper() == guid)
					return true;
			}
			return false;
		}
	}

}
